﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel.Security.Tokens;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Security.Cryptography.X509Certificates;
using PSE.Framework.Configuration;
using PSE.Framework.Common.Security.Services.Configuration;

namespace PSE.ProcessManager.Common.Service
{
    //TODO: verificar se ha possibilidade de substituir pelo componente BindingHelper da PSE
    internal class BindingFactory
    {
        //constantes para identificacao de protocolo
        private const string HttpProtocol = "Http";
        private const string TcpProtocol = "Tcp";
        private const string NamedPipeProtocol = "NamedPipe";
        private const string SERVICES_CONFIGURATION_SECTION_NAME = "servicesConfiguration";
        private const string SamlTokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1";

        private string _issuerCertificateName = string.Empty;
        private bool _createStsBinding = false;
        private SecurityTokenParameters _tokenParameters = null;

        /// <summary>
        /// Recuperar o protocolo configurado para o endpoint
        /// </summary>
        /// <param name="serviceEndpoint"></param>
        /// <returns></returns>
        internal string ResolveProtocol(ServiceEndpoint serviceEndpoint)
        {
            return ResolveProtocol(serviceEndpoint.Address);
        }

        /// <summary>
        /// Recuperar o protocolo configurado para o endpoint
        /// </summary>
        /// <param name="endpoint"></param>
        /// <returns></returns>
        internal string ResolveProtocol(EndpointAddress endpoint)
        {
            if (endpoint != null && endpoint.Uri != null)
            {
                string schema = endpoint.Uri.Scheme;

                switch (schema.ToUpper())
                {
                    case "NET.TCP":
                        return TcpProtocol;
                    case "NET.PIPE":
                        return NamedPipeProtocol;
                    case "HTTP":
                        return HttpProtocol;
                }
            }

            return string.Empty;
        }

        /// <summary>
        /// Criar um Issued Binding
        /// </summary>
        /// <param name="protocol"></param>
        /// <param name="issuerCertificateName"></param>
        /// <param name="tokenParameters"></param>
        /// <returns></returns>
        internal Binding CreateIssuedBinding(string protocol, string issuerCertificateName, SecurityTokenParameters tokenParameters)
        {
            if (string.IsNullOrEmpty(issuerCertificateName))
                throw new ArgumentNullException("issuerCertificateName");

            if (tokenParameters == null)
                throw new ArgumentNullException("tokenParameters");

            return CreateBinding(protocol, issuerCertificateName, tokenParameters);
        }

        /// <summary>
        /// Criar um binding
        /// </summary>
        /// <param name="protocol"></param>
        /// <returns></returns>
        internal Binding CreateBinding(string protocol)
        {
            return CreateBinding(protocol, null, null);
        }

        private Binding CreateBinding(string protocol, string issuerCertificateName, SecurityTokenParameters tokenParameters)
        {
            if (string.IsNullOrEmpty(protocol))
                throw new ArgumentException("protocol");

            _issuerCertificateName = issuerCertificateName;
            _tokenParameters = tokenParameters;

            if (!string.IsNullOrEmpty(issuerCertificateName) && tokenParameters != null)
                _createStsBinding = true;

            switch (protocol)
            {
                case HttpProtocol: return CreateHttpTransportBinding();
                case TcpProtocol: return CreateTcpTransportBinding();
                case NamedPipeProtocol: return CreateNamedPipeTransportBinding();
                default:
                    throw new ArgumentException("Invalid protocol {" + protocol + "}");
            }
        }
        #region Service Transport

        /// <summary>
        /// Cria um Binding sob o protocolo Http
        /// </summary>
        /// <returns>CustomBinding</returns>
        private Binding CreateHttpTransportBinding()
        {
            //TODO: Incluir o Issuer Http
            HttpTransportBindingElement httpTransport = new HttpTransportBindingElement();

            httpTransport.MaxBufferPoolSize = 122880;
            httpTransport.MaxBufferSize = Int32.MaxValue;
            httpTransport.MaxReceivedMessageSize = Int32.MaxValue;

            //Definição da Formatação da mensagem
            BinaryMessageEncodingBindingElement messageEncoding = new BinaryMessageEncodingBindingElement();

            if (_createStsBinding)
            {
                SymmetricSecurityBindingElement messageSecurity = CreateMessageSecurity();
                //Criação do CustomBinding
                return new CustomBinding(messageSecurity, messageEncoding, httpTransport);
            }
            else
            {
                return new CustomBinding(messageEncoding, httpTransport);
            }
        }

        /// <summary>
        /// Cria um Binding sob o protocolo TCP
        /// </summary>
        /// <returns>CustomBinding</returns>
        private Binding CreateTcpTransportBinding()
        {
            //Definição do protocolo de transporte do Binding
            TcpTransportBindingElement tcpTransport = new TcpTransportBindingElement();

            tcpTransport.MaxBufferPoolSize = 122880;
            tcpTransport.MaxBufferSize = Int32.MaxValue;
            tcpTransport.MaxReceivedMessageSize = Int32.MaxValue;
            tcpTransport.ConnectionBufferSize = 122880;

            //Definição da Formatação da mensagem
            BinaryMessageEncodingBindingElement messageEncoding = new BinaryMessageEncodingBindingElement();

            if (_createStsBinding)
            {
                SymmetricSecurityBindingElement messageSecurity = CreateMessageSecurity();
                //Criação do CustomBinding
                return new CustomBinding(messageSecurity, messageEncoding, tcpTransport);
            }
            else
            {
                return new CustomBinding(messageEncoding, tcpTransport);
            }
        }

        /// <summary>
        /// Cria um Binding sob o protocolo TCP
        /// </summary>
        /// <returns>CustomBinding</returns>
        private Binding CreateNamedPipeTransportBinding()
        {
            //Definição do protocolo de transporte do Binding
            NamedPipeTransportBindingElement namePipeTransport = new NamedPipeTransportBindingElement();

            namePipeTransport.MaxBufferPoolSize = 122880;
            namePipeTransport.MaxBufferSize = Int32.MaxValue;
            namePipeTransport.MaxReceivedMessageSize = Int32.MaxValue;
            namePipeTransport.ConnectionBufferSize = 122880;

            //Definição da Formatação da mensagem
            BinaryMessageEncodingBindingElement messageEncoding = new BinaryMessageEncodingBindingElement();

            if (_createStsBinding)
            {
                SymmetricSecurityBindingElement messageSecurity = CreateMessageSecurity();
                //Criação do CustomBinding
                return new CustomBinding(messageSecurity, messageEncoding, namePipeTransport);
            }
            else
            {
                return new CustomBinding(messageEncoding, namePipeTransport);
            }
        }

        private SymmetricSecurityBindingElement CreateMessageSecurity()
        {
            //Definição da segurança da mensagem.
            SymmetricSecurityBindingElement messageSecurity = new SymmetricSecurityBindingElement();

            Binding issuerBinding = null;
            EndpointAddress issuerEndPoint = null;

            GetIssuerConfiguration(out issuerEndPoint, out issuerBinding);

            //Criação do Issued Token
            messageSecurity.ProtectionTokenParameters =
                new SecureConversationSecurityTokenParameters(SecurityBindingElement.CreateIssuedTokenBindingElement(
                            new IssuedSecurityTokenParameters(SamlTokenType,
                                issuerEndPoint, issuerBinding)));

            return messageSecurity;
        }

        #endregion

        #region Issuer Transport

        /// <summary>
        /// Cria um Binding para o Issuer (STS) sob o protocolo TCP
        /// </summary>
        /// <returns>CustomBinding</returns>
        private Binding CreateTcpTransportIssuerBinding()
        {
            TcpTransportBindingElement tcpTransport = new TcpTransportBindingElement();

            SymmetricSecurityBindingElement messageSecurity = new SymmetricSecurityBindingElement();

            messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(_tokenParameters);

            X509SecurityTokenParameters x509ProtectionParameters = new X509SecurityTokenParameters();
            x509ProtectionParameters.InclusionMode = SecurityTokenInclusionMode.Never;
            messageSecurity.ProtectionTokenParameters = x509ProtectionParameters;

            SecurityBindingElement security = SecurityBindingElement.CreateSecureConversationBindingElement(messageSecurity);

            //Definição da Formatação da mensagem
            BinaryMessageEncodingBindingElement messageEncoding = new BinaryMessageEncodingBindingElement();

            return new CustomBinding(security, messageEncoding, tcpTransport);
        }

        /// <summary>
        /// Cria um Binding para o Issuer (STS) sob o protocolo Http
        /// </summary>
        /// <returns>CustomBinding</returns>
        private Binding CreateHttpTransportIssuerBinding()
        {
            HttpTransportBindingElement httpTransport = new HttpTransportBindingElement();

            httpTransport.MaxBufferPoolSize = 122880;
            httpTransport.MaxBufferSize = Int32.MaxValue;
            httpTransport.MaxReceivedMessageSize = Int32.MaxValue;

            SymmetricSecurityBindingElement messageSecurity = new SymmetricSecurityBindingElement();

            messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(_tokenParameters);

            X509SecurityTokenParameters x509ProtectionParameters = new X509SecurityTokenParameters();
            x509ProtectionParameters.InclusionMode = SecurityTokenInclusionMode.Never;
            messageSecurity.ProtectionTokenParameters = x509ProtectionParameters;
            return new CustomBinding(messageSecurity, httpTransport);
        }

        /// <summary>
        /// Retorna o endereço do Emissor de Token configurado.
        /// </summary>
        /// <returns></returns>
        private EndpointAddress RetrieveIssuerEndpoint()
        {
            ApplicationConfiguration config = ApplicationConfiguration.GetCurrent();

            string endpointAddress = config.GetSection<ServicesConfigurationSection>(SERVICES_CONFIGURATION_SECTION_NAME).IssuerAddress;
            if (String.IsNullOrEmpty(endpointAddress))
                throw new ArgumentNullException("The issuer address configuration is not set");

            return new EndpointAddress(new Uri(endpointAddress), new DnsEndpointIdentity(_issuerCertificateName));

        }

        /// <summary>
        /// Monta o endpoint e o Binding Configurado para o Issuer.
        /// </summary>
        /// <param name="endpoint"></param>
        /// <param name="binding"></param>
        private void GetIssuerConfiguration(out EndpointAddress endpoint, out Binding binding)
        {
            endpoint = RetrieveIssuerEndpoint();

            if (endpoint.Uri.AbsoluteUri.ToUpper().StartsWith("HTTP"))
                binding = CreateHttpTransportIssuerBinding();
            else
                binding = CreateTcpTransportIssuerBinding();
        }

        #endregion
    }
}
